iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Software Development

C++ 套件管理器求生指南 Conan vs Vcpkg系列 第 25 篇

D25:Conan 動態庫部署 deployer

  • 分享至 

  • xImage
  •  

「在我的電腦上沒問題啊?」

這個程式設計師的迷因回答實在太經典了。

很多 Windows C++ 開發者都遇過這個瞬間:編譯成功,按下 F5,程式可以正常跑。然後你興沖沖地把 .exe 傳給同事,對方點下後卻看到「找不到 xxx.dll,程式無法啟動」。

這是函式庫使用時,很常碰到的部署問題。

用 Conan 的人平常不太會遇到這個問題,是因為 ConanCenter 上大部分的套件預設都是靜態連結(shared=False),所以不需要找動態函式庫(.dll/.so/.dylib)。但今天的故事,要進入一個情境:你刻意選擇用動態連結函式庫。

比如說,程式使用了 OpenSSL 加密函式庫,就是一個典型的例子。它跟安全性有關,三不五時會出現漏洞公告。如果是靜態連結,要修正這些漏洞,每次都要重新編譯整個專案,再重新發布整個程式。但是如果是用 DLL 呢?只要換掉一個 DLL 就能修好漏洞了。

所以,只要用到動態函式庫,就要解決程式啟動時怎麼找 DLL 的問題。今天就來看看 Conan 套件管理器在這方面能幫上什麼忙。

一、案發現場:OpenSSL 小程式

我們這裡用一個 Windows 小程式作範例。

這個程式使用 Conan 管理套件,導入了 OpenSSL 加密函式庫。

conanfile.txt 中,我們想用動態函式庫,所以在 [options] 區段設定 shared=True:

[requires]
openssl/4.0.3

[options]
openssl/*:shared=True

[generators]
CMakeDeps
CMakeToolchain

CMakeLists.txt

cmake_minimum_required(VERSION 3.23)
project(my_app C)
find_package(OpenSSL REQUIRED)
add_executable(my_app main.c)
target_link_libraries(my_app OpenSSL::Crypto)
install(TARGETS my_app)

main.c

#include <stdio.h>
#include <openssl/crypto.h>

int main(void) {
    printf("%s\n", OpenSSL_version(OPENSSL_VERSION));
    return 0;
}

安裝依賴並編譯專案:

conan install . -of=build/Release -s build_type=Release --build=missing
cmake --preset conan-default
cmake --build --preset conan-release

ConanCenter 上面有預編譯好的 OpenSSL 4.0.3 二進位檔,可直接下載,不用自己編譯。

專案編譯很順利,但是接下來執行 build\Release\Release\my_app.exe 時,程式馬上爆出錯誤,錯誤碼是:0xC0000135,意思是 Windows 找不到需要的 OpenSSL DLL。

所以 OpenSSL 的 DLL 檔案到底在哪裡呀?原來 OpenSSL 的兩個動態函式庫都藏在 Conan 的本機快取裡面,在 OpenSSL 套件的 bin 目錄下。Windows 找 DLL 時,主要看執行檔所在的資料夾、系統目錄和 PATH,Conan 快取不在這些地方,當然找不到。

如果你是用 Visual Studio 按 F5 執行,倒是可以順利跑起來,因為 Conan 產生的 conan_toolchain.cmake 會把套件的 bin 目錄設定進 Visual Studio 的偵錯環境裡。不過這只有在用 CMake 產生 Visual Studio 專案時才有效。

怎麼辦呢?

二、開發時的解法:conanrun.bat/.sh

呼叫 conan install 指令之後,其實輸出目錄下會出現一個 conanrun.bat 批次檔,它是 Conan 預設啟用的 VirtualRunEnv generator 產生的。(macOS/Linux 上是 conanrun.sh,要用 source 執行,設定的是 LD_LIBRARY_PATH/DYLD_LIBRARY_PATH。)

只要在執行 exe 之前,在命令列裡面,先執行它,再啟動程式:

build\Release\conanrun.bat
build\Release\Release\my_app.exe

程式就能順利跑起來了,印出 OpenSSL 4.0.3 29 Sep 2026。找不到 DLL 的錯誤消失了。

conanrun.bat 做的事情,就是把各套件的 bin 目錄加入環境變數 PATH 裡面,只在當前命令列視窗有效。

這種方式適合本機開發、測試、或者 CI/CD 環境。

但是終端使用者,肯定沒辦法執行 conanrun.bat 呀,所以這不是最終的部署方案。但是開發用,夠了。

記得在 Windows 上面,debug 和 release 要各自安裝到不同的輸出目錄,這樣 conanrun.bat 不會互相覆蓋,而且可以避免 debug 程式載入 release DLL 這類可能導致很難排查的 Heap Corruption 錯誤。

這個 conanrun.bat (conanrun.sh) 是 VirtualRunEnv 產生的,有興趣可用此關鍵字往下查。

三、出貨解法一號:runtime_deploy

VirtualRunEnv 讓程式去本機快取裡面找 DLL,但是真正軟體出貨時,應該要反過來,把檔案從快取複製出來,放在執行檔的旁邊,跟著一起打包,這樣才是正解。

Conan 內建的 runtime_deploy,就是專門複製執行期需要的檔案:

> conan install . -of=build/Release -s build_type=Release --deployer=runtime_deploy --deployer-folder=dist/Release

runtime_deploy: WARN: This deployer is experimental and subject to change.
runtime_deploy: Copied 3 files from openssl/4.0.3

這裡我們在安裝指令裡面加了兩個參數:

  • --deployer=runtime_deploy:把每個套件 bin 目錄裡的檔案,全部複製到指定目錄。
  • --deployer-folder=dist/Release:設定複製的目的地。

跑完之後,dist\Release 目錄裡面就會有三個檔案囉:

dist\Release\
├── libcrypto-4-x64.dll
├── libssl-4-x64.dll
└── openssl.exe

要注意,這個指令只會複製相依套件的檔案,不會複製我們自己的程式。要讓 my_app.exe 跟 DLL 進同一個目錄,要嘛自己指定專案編譯的輸出位置,要嘛自己把 exe 複製進去。

在 mac/Linux 上,runtime_deploy 一樣會把 .so/.dylib 平鋪複製出來,但執行檔預設不會到自己旁邊找函式庫,要在 CMake 把 RPATH 設成 $ORIGIN(macOS 是 @loader_path),這一步 Conan 不會幫你。

目前 runtime_deploy 仍是實驗功能,之後用法可能會變。

四、出貨解法二號:recipe 裡的 deploy()

如果你的程式本身也做成 Conan 套件,用 Conan 來編譯的話,就可以在 recipe 裡覆寫 deploy() 方法,完全自訂部署的行為。

這時要把原本的 conanfile.txt 換成下面這份 conanfile.py(recipe 的寫法可以回頭參考 D15):

import os
from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout
from conan.tools.files import copy

class MyAppConan(ConanFile):
    name = "my_app"
    version = "1.0"
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"
    exports_sources = "CMakeLists.txt", "main.c"
    default_options = {"openssl/*:shared": True}

    def requirements(self):
        self.requires("openssl/4.0.3")

    def layout(self):
        cmake_layout(self)

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def package(self):
        cmake = CMake(self)
        cmake.install()

    def deploy(self):
        copy(self, "*.exe", os.path.join(self.package_folder, "bin"), self.deploy_folder)
        for dep in self.dependencies.host.values():
            for bindir in dep.cpp_info.bindirs:
                copy(self, "*.dll", bindir, self.deploy_folder)

在專案目錄下執行以下指令:

conan create . --build=missing
conan install --requires=my_app/1.0 --deployer-package="my_app/*" --deployer-folder=dist

接著去看 dist 目錄,就會發現目錄底下已經有 my_app.exe、libcrypto-4-x64.dll、libssl-4-x64.dll,直接執行就能印出 OpenSSL 版本了。

要注意寫法,只寫 --deployer-package=my_app 不行,而且 Conan 不會報錯。要寫 --deployer-package="my_app/*" 或 --deployer-package=my_app/1.0。

小結

Conan 在部署上能幫的忙:

  1. 開發時用 VirtualRunEnv 產生的 conanrun.bat,就可以讓程式找到 DLL
  2. 出貨時用 runtime_deploy 把 DLL 複製出來
  3. 把程式做成套件,在 recipe 寫 deploy() 可以完全自己決定如何部署

上一篇
D24:vcpkg 怎麼升級套件版本?套件更新機制解密
下一篇
D26:vcpkg DLL動態庫部署
系列文
C++ 套件管理器求生指南 Conan vs Vcpkg 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言